<template>
	<el-table
		ref="myTable"
		:border="true"
		class="my-table flex-grow-1"
		:data="tableData"
		:row-style="setRowStyle"
		v-bind="$attrs"
		@header-dragend="handleHeaderDragend"
	>
		<template v-for="col in displayColumns" :key="`${col.prop}-${col.type}`">
			<!-- col 没有children 属性 -->
			<el-table-column v-if="!col.children" resizable v-bind="{ headerAlign: 'center', ...col }">
				<template v-if="!col.type" #header="scope: { column: any; $index: number }">
					<slot :name="`${col.prop || 'default'}_header`" v-bind="{ ...scope, prop: col.prop }">
						<template v-if="!col.filter">
							{{ col.label }}
						</template>
						<template v-else>
							<el-popover trigger="click" :width="300">
								<template #reference>
									<span class="items-center display-inline-flex">
										{{ col.label }}
										<my-icon
											icon="Filter"
											class="pointer m-l-5"
											:color="queryParams[col.filter.prop] ? 'var(--el-color-primary)' : 'var(--el-table-header-text-color)'"
										/>
									</span>
								</template>
								<my-form-item
									class="m-b--18 m-l--12"
									:model="queryParams"
									:form-item="[
										Object.assign(
											{
												onChange: () => {
													searchFn?.();
												}
											},
											col.filter
										)
									]"
								/>
							</el-popover>
						</template>
					</slot>
				</template>

				<!-- col 没有type，formatter属性时，使用 slot 插槽-->
				<template v-if="!col.type && !col.formatter" #default="scope: { row: any; column: any; $index: number }">
					<slot :name="`${col.prop || 'default'}`" v-bind="{ ...scope, prop: col.prop }">
						{{ valueFn(scope.row, col.prop) }}
					</slot>
				</template>
			</el-table-column>
			<!-- col 有children 属性， 直接返回slot插槽 -->
			<slot v-else :name="col.prop" :column="col"></slot>
		</template>
		<el-table-column
			v-if="columnFilter"
			prop="ColumnFilter"
			:resizable="false"
			width="26px"
			align="center"
			class-name="my-table--not-drag"
			fixed="right"
		>
			<template #header>
				<column-filter
					v-model="displayColumnProps"
					:columns="columnsProxy"
					@filter-reset-click="filterResetClick"
				/>
			</template>
		</el-table-column>
		<!--暴露 el-table append 插槽-->
		<template v-if="$slots.append" #append>
			<slot name="append"></slot>
		</template>
		<!-- 自动无数据提示 插槽-->
		<template #empty>
			<slot name="empty">
				<el-empty description="暂无数据" />
			</slot>
		</template>
	</el-table>
</template>

<script setup name="MyTable" lang="ts">
import ColumnFilter from './ColumnFilter.vue';
/**
 * MyTable 表格组件 报错问题为无法获取使用$props
 * @module components/my-table
 */
import { type CSSProperties } from 'vue';
import Sortable, { type SortableEvent } from 'sortablejs';
import { debounce } from '@/utils/util';
import { insertAfter } from '@/utils/dom';
import { type ElTable } from 'element-plus';

interface TableSlots {
	// 列插槽（根据 prop 动态生成）
	[key: `${string}`]: (_props: { row: any; column: any; $index: number; prop: string | undefined }) => any;
	// 表头插槽
	[key: `${string}_header`]: (_props: { column: any; $index: number; prop: string | undefined }) => any;
	// 表尾插槽
	append: (_props?: { row: any; column: any; $index: number; prop: string | undefined }) => any;
	// 空值插槽
	empty: (_props?: { row: any; column: any; $index: number; prop: string | undefined }) => any;
}
defineSlots<TableSlots>();

const emits = defineEmits(['on-column-sort', 'on-row-sort', 'on-reach-bottom', 'on-reach-top', 'on-scroll']),
	/**
	 * 开启表格列自定义
	 * <my-table :data="state.list" :columns="state.columns" columnFilter @selection-change="(val) => (tableSelection = val)"></my-table>
	 * state.columns=[{label: '配置名称',prop: 'configName'}]
	 *
	 * 开启表格列排序
	 * <my-table :data="state.list" :columns="state.columns" @selection-change="(val) => (tableSelection = val)"></my-table>
	 * state.columns=[{label: '配置名称',prop: 'configName',sortable: true}]
	 *
	 * 开启表格头部搜索
	 * <my-form :searchFn="() => {filterKeysFn();	refTable.reload();}"/>
	 * <my-table :data="state.list" :columns="state.columns" @selection-change="(val) => (tableSelection = val)" :queryParams="queryParams" :searchFn="() => refTable.reload()"></my-table>
	 * state.columns=[{label: '配置名称',prop: 'configName',filter:{prop: 'likeConfigName'}}]
	 *
	 * 属性参数，完全继承 el-table参数，并有以下扩展参数
	 * @property {Array} [columns = []]  表格列定义，对象属性参数完全继承 el-table-column
	 * @property {String} [columnsKey = '_columns']  保存用户自定义表格列的唯一标识
	 * @property {Array} [data = []]  表格数据
	 * @property {Object | Boolean} [columnSortable = false] 是否启用列拖拽排序, 可以配置Sortable个性化参数
	 * @property {Object | Boolean} [rowSortable = false] 是否启用行拖拽排序, 可以配置Sortable个性化参数
	 * @property {Number} [distanceToButton = 50] 滚动到距离底部多少距离触发 on-reach-bottom 事件， table需要设置高度才有效
	 * @property {Boolean} [columnFilter] table列的自定义功能，默认为false
	 */
	props = defineProps({
		data: {
			type: Array,
			default: () => []
		},
		columns: {
			type: Array as PropType<columnsType[]>,
			default: () => []
		},
		columnsKey: {
			type: String,
			default: 'columns'
		},
		columnSortable: {
			type: [Object, Boolean],
			default: false
		},
		rowSortable: {
			type: [Object, Boolean],
			default: false
		},
		distanceToButton: {
			type: Number,
			default: 50
		},
		columnFilter: {
			type: Boolean,
			default: false
		},
		/***表头查询功能****/
		queryParams: {
			type: Object as PropType<objType>,
			default: () => {}
		},
		searchFn: {
			type: Function
		},
		/***表格底部合计****/
		sum: {
			type: Boolean,
			default: false
		}
	}),
	$route = useRoute();

// 表格列定义数组
const myTable = shallowRef<InstanceType<typeof ElTable>>(),
	columnsKey = `${$route.path}_${props.columnsKey}`,
	displayColumnProps = ref<string[]>([]),
	// 表格行数据
	tableData = ref<any[]>([]);

// 原始表格数据
let columnsProxy = ref<columnsType[]>([]),
	// 列拖拽Sortable实例
	columnSortableInstance: any = null,
	// 行拖拽Sortable实例
	rowSortableInstance: any = null,
	// 上次滚动的位置
	lastScrollTop = 0;
/**
 * 监听表格数据
 */
watch(
	() => props.data,
	(val) => {
		tableData.value = val;
	},
	{ immediate: true }
);

/**
 * 实际表格渲染的列数组
 */
const displayColumns = computed(() =>
	columnsProxy.value.filter((col: columnsType) => {
		// 有type的字段 或 没设置属性名称的列固定显示
		if (col.type || !col.prop) return true;
		return displayColumnProps.value.includes(col.prop);
	})
);
/**
 * 计算表头显示的列prop数组值
 */
function resetDisplayColumns() {
	displayColumnProps.value = columnsProxy.value
		.filter((col) => {
			if (!col.prop || col.type) return false;
			return col.display !== false;
		})
		.map((col) => col.prop!);
}
/**
 * 监听表头显示列的数据，从而改变原始props.columns的display值
 */
watch(
	() => displayColumnProps.value,
	(val) => {
		columnsProxy.value.forEach((item) => {
			if (!(item.type || !item.prop)) {
				// 默认为显示修改
				if (val.includes(item.prop)) {
					if (item.display === false) delete item.display;
				} else {
					item.display = false;
				}
			}
		});
		$store.user.SetColumns(columnsKey, columnsProxy.value);
	}
);

/**
 * 监听表格column列数据
 */
watch(
	() => props.columns,
	(val) => {
		if (props.columnFilter && $store.user.GetColumns(columnsKey)) {
			val.forEach((item) => {
				const { width, display } = $store.user.GetColumns(columnsKey).find((i: columnsType) => i.prop === item.prop);
				if (width) item.width = width;
				if (display) item.display = display;
			});
		}
		columnsProxy.value = [...val];
		resetDisplayColumns();
	},
	{ immediate: true, deep: true }
);

function handleHeaderDragend(newWidth: number, oldWidth: number, column: any) {
	columnsProxy.value[column.getColumnIndex()].width = newWidth;
}
/**
 * 自定义表头重置逻辑
 */
function filterResetClick() {
	props.columns.forEach((item, i) => {
		const { width, display } = columnsProxy.value[i] ?? {};
		if (width) {
			item.width = width;
		} else {
			delete item.width;
		}
		if (display) {
			item.display = display;
		} else {
			delete item.display;
		}
	});
}
/**
 * 表格的多层字段值显示函数
 * @param {*} row
 * @param {*} key
 */
function valueFn(row: any, key: string | undefined) {
	const keyArray = key?.split('.') || [];
	let data: any = '';
	keyArray.forEach((item, index) => {
		if (index === 0) {
			data = row[item];
		} else {
			data = data?.[item];
		}
	});
	return data;
}

/**
 * 内部调用排序方法，还原由Sortable拖拽改变的DOM顺序，然后修改数据，再由数据驱动改变DOM
 * @function
 * @private
 * @param {String} type 排序类型，row 或 column
 * @param {HTMLElement} container 容器元素DOM
 * @param {Array} data 数据数组
 * @param {Object} e Sortable事件对象
 */
function sort(type: 'row' | 'column', container: HTMLElement, data: any[], e: SortableEvent) {
	if (!container || !data || !e) return;
	container.removeChild(e.item);
	const tempData = type === 'column' ? [...data.filter((item) => item.display !== false)] : [...data],
		newIndex = e.newIndex as number,
		oldIndex = e.oldIndex as number,
		newTemp = tempData[newIndex],
		oldTemp = tempData[oldIndex],
		children = container.children;
	if (children) {
		container.appendChild(e.item);
	} else {
		oldIndex > 0 ? insertAfter(e.item, children[oldIndex - 1] as HTMLElement) : container.insertBefore(e.item, children[0]);
	}
	// 固定类型的列不运行拖拽和放置
	if (type === 'column' && data[newIndex] && data[newIndex].type && data[newIndex].fixed) return;
	[tempData[newIndex], tempData[oldIndex]] = [tempData[oldIndex], tempData[newIndex]];
	// 解决nextTick执行顺序问题,调整拖拽顺序后，change事件返回的数据未更新
	return new Promise((resolve) => {
		// 修复ele中重新设置表格配置时要重新描绘表格
		if (type === 'column') {
			const copyData = [...data],
				newIndex = copyData.findIndex((item) => item.label === newTemp.label && item.prop === newTemp.prop),
				oldIndex = copyData.findIndex((item) => item.label === oldTemp.label && item.prop === oldTemp.prop);
			[copyData[newIndex], copyData[oldIndex]] = [copyData[oldIndex], copyData[newIndex]];
			columnsProxy.value = [];
			nextTick(() => {
				columnsProxy.value = copyData;
				resolve(columnsProxy.value);
			});
		} else {
			tableData.value = [];
			nextTick(() => {
				tableData.value = tempData;
				resolve(tableData.value);
			});
		}
	});
}
/**
 * 实例化列Sortable
 */
function initColumnSortable() {
	if (!myTable.value) return;
	const tr: HTMLElement = (myTable.value.$refs.tableHeader as HTMLElement).getElementsByTagName('tr')[0];
	if (!tr) return;
	const options = Object.assign(
		{
			forceFallback: false,
			animation: 150
		},
		props.columnSortable,
		{
			handle: '.cell',
			filter: '.my-table--not-drag',
			onSort: (e: SortableEvent) => {
				sort('column', tr, columnsProxy.value, e)?.then(() => {
					/**
					 * 列拖拽排序完成时触发
					 * @event on-column-sort
					 * @param {object} e Sortable事件对象
					 * @param {Array} columns 列定义数组
					 */
					emits('on-column-sort', e, columnsProxy.value);
				});
			}
		}
	);
	columnSortableInstance = new Sortable(tr, options);
}
/**
 * 实例化行Sortable
 */
function initRowSortable() {
	if (!myTable.value) return;
	const tbody: HTMLElement = (myTable.value.$refs.bodyWrapper as HTMLElement).getElementsByTagName('tbody')[0];
	if (!tbody) return;
	const options = Object.assign(
		{
			forceFallback: false,
			animation: 150
		},
		props.rowSortable,
		{
			onSort: (e: SortableEvent) => {
				sort('row', tbody, tableData.value, e)?.then(() => {
					/**
					 * 行拖拽排序完成时触发
					 * @event on-row-sort
					 * @param {object} e Sortable事件对象
					 * @param {Array} data 行数据
					 */
					emits('on-row-sort', e, tableData.value);
				});
			}
		}
	);
	rowSortableInstance = new Sortable(tbody, options);
}
/**
 * 表格滚动加载数据逻辑
 */
function handleScroll(e: any) {
	const target = e.target;
	const dir = target.scrollTop - lastScrollTop > 0 ? 'down' : 'up';
	lastScrollTop = target.scrollTop;
	const scrollDistance = target.scrollHeight - target.scrollTop - target.clientHeight;
	if (dir === 'down' && scrollDistance <= props.distanceToButton) {
		/**
		 * 表格滚动到底部时触发
		 * @event on-reach-bottom
		 * @param {Number} scrollDistance 离底部的距离
		 */
		emits('on-reach-bottom', scrollDistance);
	}
	if (dir === 'up' && target.scrollTop <= props.distanceToButton) {
		/**
		 * 表格滚动到顶部时触发
		 * @event on-reach-top
		 * @param {Number} scrollTop 离顶部的距离
		 */
		emits('on-reach-top', target.scrollTop);
	}
	/**
	 * 表格滚动时触发
	 * @event on-scroll
	 * @param {Object} e 事件对象
	 */
	emits('on-scroll', e);
}

/**
 * 表格底部合计
 */
function setRowStyle({ row }: { row: objType }): CSSProperties {
	if (props.sum === false || !(row.amount === 0 || Boolean(row.amount))) return {};

	return {
		background: '#f5f9fd',
		fontWeight: 'bold',
		position: 'sticky',
		zIndex: 2,
		bottom: row.amount === 0 ? '41px' : 0
	};
}

type listenerType = () => void;
let proxyHandleScroll: listenerType, bodyWrapper: HTMLElement | null;
onMounted(() => {
	nextTick(() => {
		props.columnSortable && initColumnSortable();
		props.rowSortable && initRowSortable();
		proxyHandleScroll = debounce(handleScroll, 20, false) as listenerType;
		if (myTable.value) {
			bodyWrapper = (myTable.value.$refs.bodyWrapper as HTMLElement).querySelector('.el-scrollbar .el-scrollbar__wrap');
			if (bodyWrapper) bodyWrapper.addEventListener('scroll', proxyHandleScroll);
		}
	});
});

onBeforeUnmount(() => {
	columnSortableInstance?.destroy?.();
	rowSortableInstance?.destroy?.();
	if (bodyWrapper && proxyHandleScroll) bodyWrapper.removeEventListener('scroll', proxyHandleScroll);
});
</script>

<style lang="scss">
.my-table {
	.sortable-ghost {
		opacity: 0.3;
	}

	.sortable-ghost.my-table--not-drag {
		background: transparent;
	}

	.my-table--not-drag {
		.cell {
			padding: 0;
			cursor: pointer;
		}
	}

	&-filter {
		&__label {
			font-style: normal;
		}
	}
}
</style>
